using ICSharpCode.SharpZipLib.Checksums;
using ICSharpCode.SharpZipLib.Zip.Compression.Streams;
using System;
namespace ICSharpCode.SharpZipLib.Zip.Compression
{
	public class Inflater
	{
		private const int DECODE_HEADER = 0;
		private const int DECODE_DICT = 1;
		private const int DECODE_BLOCKS = 2;
		private const int DECODE_STORED_LEN1 = 3;
		private const int DECODE_STORED_LEN2 = 4;
		private const int DECODE_STORED = 5;
		private const int DECODE_DYN_HEADER = 6;
		private const int DECODE_HUFFMAN = 7;
		private const int DECODE_HUFFMAN_LENBITS = 8;
		private const int DECODE_HUFFMAN_DIST = 9;
		private const int DECODE_HUFFMAN_DISTBITS = 10;
		private const int DECODE_CHKSUM = 11;
		private const int FINISHED = 12;
		private static readonly int[] CPLENS = new int[]
		{
			3,
			4,
			5,
			6,
			7,
			8,
			9,
			10,
			11,
			13,
			15,
			17,
			19,
			23,
			27,
			31,
			35,
			43,
			51,
			59,
			67,
			83,
			99,
			115,
			131,
			163,
			195,
			227,
			258
		};
		private static readonly int[] CPLEXT = new int[]
		{
			0,
			0,
			0,
			0,
			0,
			0,
			0,
			0,
			1,
			1,
			1,
			1,
			2,
			2,
			2,
			2,
			3,
			3,
			3,
			3,
			4,
			4,
			4,
			4,
			5,
			5,
			5,
			5,
			0
		};
		private static readonly int[] CPDIST = new int[]
		{
			1,
			2,
			3,
			4,
			5,
			7,
			9,
			13,
			17,
			25,
			33,
			49,
			65,
			97,
			129,
			193,
			257,
			385,
			513,
			769,
			1025,
			1537,
			2049,
			3073,
			4097,
			6145,
			8193,
			12289,
			16385,
			24577
		};
		private static readonly int[] CPDEXT = new int[]
		{
			0,
			0,
			0,
			0,
			1,
			1,
			2,
			2,
			3,
			3,
			4,
			4,
			5,
			5,
			6,
			6,
			7,
			7,
			8,
			8,
			9,
			9,
			10,
			10,
			11,
			11,
			12,
			12,
			13,
			13
		};
		private int mode;
		private int readAdler;
		private int neededBits;
		private int repLength;
		private int repDist;
		private int uncomprLen;
		private bool isLastBlock;
		private long totalOut;
		private long totalIn;
		private bool noHeader;
		private StreamManipulator input;
		private OutputWindow outputWindow;
		private InflaterDynHeader dynHeader;
		private InflaterHuffmanTree litlenTree;
		private InflaterHuffmanTree distTree;
		private Adler32 adler;
		public bool IsNeedingInput
		{
			get
			{
				return this.input.IsNeedingInput;
			}
		}
		public bool IsNeedingDictionary
		{
			get
			{
				return this.mode == 1 && this.neededBits == 0;
			}
		}
		public bool IsFinished
		{
			get
			{
				return this.mode == 12 && this.outputWindow.GetAvailable() == 0;
			}
		}
		public int Adler
		{
			get
			{
				return this.IsNeedingDictionary ? this.readAdler : ((int)this.adler.Value);
			}
		}
		public long TotalOut
		{
			get
			{
				return this.totalOut;
			}
		}
		public long TotalIn
		{
			get
			{
				return this.totalIn - (long)this.RemainingInput;
			}
		}
		public int RemainingInput
		{
			get
			{
				return this.input.AvailableBytes;
			}
		}
		public Inflater() : this(false)
		{
		}
		public Inflater(bool noHeader)
		{
			this.noHeader = noHeader;
			this.adler = new Adler32();
			this.input = new StreamManipulator();
			this.outputWindow = new OutputWindow();
			this.mode = (noHeader ? 2 : 0);
		}
		public void Reset()
		{
			this.mode = (this.noHeader ? 2 : 0);
			this.totalIn = 0L;
			this.totalOut = 0L;
			this.input.Reset();
			this.outputWindow.Reset();
			this.dynHeader = null;
			this.litlenTree = null;
			this.distTree = null;
			this.isLastBlock = false;
			this.adler.Reset();
		}
		private bool DecodeHeader()
		{
			int num = this.input.PeekBits(16);
			bool result;
			if (num < 0)
			{
				result = false;
			}
			else
			{
				this.input.DropBits(16);
				num = ((num << 8 | num >> 8) & 65535);
				if (num % 31 != 0)
				{
					throw new SharpZipBaseException("Header checksum illegal");
				}
				if ((num & 3840) != 2048)
				{
					throw new SharpZipBaseException("Compression Method unknown");
				}
				if ((num & 32) == 0)
				{
					this.mode = 2;
				}
				else
				{
					this.mode = 1;
					this.neededBits = 32;
				}
				result = true;
			}
			return result;
		}
		private bool DecodeDict()
		{
			bool result;
			while (this.neededBits > 0)
			{
				int num = this.input.PeekBits(8);
				if (num < 0)
				{
					result = false;
					return result;
				}
				this.input.DropBits(8);
				this.readAdler = (this.readAdler << 8 | num);
				this.neededBits -= 8;
			}
			result = false;
			return result;
		}
		private bool DecodeHuffman()
		{
			int i = this.outputWindow.GetFreeSpace();
			bool result;
			while (i >= 258)
			{
				int symbol;
				switch (this.mode)
				{
				case 7:
					while (((symbol = this.litlenTree.GetSymbol(this.input)) & -256) == 0)
					{
						this.outputWindow.Write(symbol);
						if (--i < 258)
						{
							result = true;
							return result;
						}
					}
					if (symbol >= 257)
					{
						try
						{
							this.repLength = Inflater.CPLENS[symbol - 257];
							this.neededBits = Inflater.CPLEXT[symbol - 257];
						}
						catch (Exception)
						{
							throw new SharpZipBaseException("Illegal rep length code");
						}
						goto IL_B9;
					}
					if (symbol < 0)
					{
						result = false;
						return result;
					}
					this.distTree = null;
					this.litlenTree = null;
					this.mode = 2;
					result = true;
					return result;
				case 8:
					goto IL_B9;
				case 9:
					goto IL_113;
				case 10:
					break;
				default:
					throw new SharpZipBaseException("Inflater unknown mode");
				}
				IL_159:
				if (this.neededBits > 0)
				{
					this.mode = 10;
					int num = this.input.PeekBits(this.neededBits);
					if (num < 0)
					{
						result = false;
						return result;
					}
					this.input.DropBits(this.neededBits);
					this.repDist += num;
				}
				this.outputWindow.Repeat(this.repLength, this.repDist);
				i -= this.repLength;
				this.mode = 7;
				continue;
				IL_113:
				symbol = this.distTree.GetSymbol(this.input);
				if (symbol >= 0)
				{
					try
					{
						this.repDist = Inflater.CPDIST[symbol];
						this.neededBits = Inflater.CPDEXT[symbol];
					}
					catch (Exception)
					{
						throw new SharpZipBaseException("Illegal rep dist code");
					}
					goto IL_159;
				}
				result = false;
				return result;
				IL_B9:
				if (this.neededBits > 0)
				{
					this.mode = 8;
					int num = this.input.PeekBits(this.neededBits);
					if (num < 0)
					{
						result = false;
						return result;
					}
					this.input.DropBits(this.neededBits);
					this.repLength += num;
				}
				this.mode = 9;
				goto IL_113;
			}
			result = true;
			return result;
		}
		private bool DecodeChksum()
		{
			bool result;
			while (this.neededBits > 0)
			{
				int num = this.input.PeekBits(8);
				if (num < 0)
				{
					result = false;
					return result;
				}
				this.input.DropBits(8);
				this.readAdler = (this.readAdler << 8 | num);
				this.neededBits -= 8;
			}
			if ((int)this.adler.Value != this.readAdler)
			{
				throw new SharpZipBaseException(string.Concat(new object[]
				{
					"Adler chksum doesn't match: ",
					(int)this.adler.Value,
					" vs. ",
					this.readAdler
				}));
			}
			this.mode = 12;
			result = false;
			return result;
		}
		private bool Decode()
		{
			bool result;
			switch (this.mode)
			{
			case 0:
				result = this.DecodeHeader();
				return result;
			case 1:
				result = this.DecodeDict();
				return result;
			case 2:
				if (this.isLastBlock)
				{
					if (this.noHeader)
					{
						this.mode = 12;
						result = false;
						return result;
					}
					this.input.SkipToByteBoundary();
					this.neededBits = 32;
					this.mode = 11;
					result = true;
					return result;
				}
				else
				{
					int num = this.input.PeekBits(3);
					if (num < 0)
					{
						result = false;
						return result;
					}
					this.input.DropBits(3);
					if ((num & 1) != 0)
					{
						this.isLastBlock = true;
					}
					switch (num >> 1)
					{
					case 0:
						this.input.SkipToByteBoundary();
						this.mode = 3;
						break;
					case 1:
						this.litlenTree = InflaterHuffmanTree.defLitLenTree;
						this.distTree = InflaterHuffmanTree.defDistTree;
						this.mode = 7;
						break;
					case 2:
						this.dynHeader = new InflaterDynHeader();
						this.mode = 6;
						break;
					default:
						throw new SharpZipBaseException("Unknown block type " + num);
					}
					result = true;
					return result;
				}
				break;
			case 3:
				if ((this.uncomprLen = this.input.PeekBits(16)) < 0)
				{
					result = false;
					return result;
				}
				this.input.DropBits(16);
				this.mode = 4;
				break;
			case 4:
				break;
			case 5:
				goto IL_1E7;
			case 6:
				if (!this.dynHeader.Decode(this.input))
				{
					result = false;
					return result;
				}
				this.litlenTree = this.dynHeader.BuildLitLenTree();
				this.distTree = this.dynHeader.BuildDistTree();
				this.mode = 7;
				goto IL_279;
			case 7:
			case 8:
			case 9:
			case 10:
				goto IL_279;
			case 11:
				result = this.DecodeChksum();
				return result;
			case 12:
				result = false;
				return result;
			default:
				throw new SharpZipBaseException("Inflater.Decode unknown mode");
			}
			int num2 = this.input.PeekBits(16);
			if (num2 < 0)
			{
				result = false;
				return result;
			}
			this.input.DropBits(16);
			if (num2 != (this.uncomprLen ^ 65535))
			{
				throw new SharpZipBaseException("broken uncompressed block");
			}
			this.mode = 5;
			IL_1E7:
			int num3 = this.outputWindow.CopyStored(this.input, this.uncomprLen);
			this.uncomprLen -= num3;
			if (this.uncomprLen == 0)
			{
				this.mode = 2;
				result = true;
				return result;
			}
			result = !this.input.IsNeedingInput;
			return result;
			IL_279:
			result = this.DecodeHuffman();
			return result;
		}
		public void SetDictionary(byte[] buffer)
		{
			this.SetDictionary(buffer, 0, buffer.Length);
		}
		public void SetDictionary(byte[] buffer, int index, int count)
		{
			if (buffer == null)
			{
				throw new ArgumentNullException("buffer");
			}
			if (index < 0)
			{
				throw new ArgumentOutOfRangeException("index");
			}
			if (count < 0)
			{
				throw new ArgumentOutOfRangeException("count");
			}
			if (!this.IsNeedingDictionary)
			{
				throw new InvalidOperationException("Dictionary is not needed");
			}
			this.adler.Update(buffer, index, count);
			if ((int)this.adler.Value != this.readAdler)
			{
				throw new SharpZipBaseException("Wrong adler checksum");
			}
			this.adler.Reset();
			this.outputWindow.CopyDict(buffer, index, count);
			this.mode = 2;
		}
		public void SetInput(byte[] buffer)
		{
			this.SetInput(buffer, 0, buffer.Length);
		}
		public void SetInput(byte[] buffer, int index, int count)
		{
			this.input.SetInput(buffer, index, count);
			this.totalIn += (long)count;
		}
		public int Inflate(byte[] buffer)
		{
			if (buffer == null)
			{
				throw new ArgumentNullException("buffer");
			}
			return this.Inflate(buffer, 0, buffer.Length);
		}
		public int Inflate(byte[] buffer, int offset, int count)
		{
			if (buffer == null)
			{
				throw new ArgumentNullException("buffer");
			}
			if (count < 0)
			{
				throw new ArgumentOutOfRangeException("count", "count cannot be negative");
			}
			if (offset < 0)
			{
				throw new ArgumentOutOfRangeException("offset", "offset cannot be negative");
			}
			if (offset + count > buffer.Length)
			{
				throw new ArgumentException("count exceeds buffer bounds");
			}
			int result;
			if (count == 0)
			{
				if (!this.IsFinished)
				{
					this.Decode();
				}
				result = 0;
			}
			else
			{
				int num = 0;
				do
				{
					if (this.mode != 11)
					{
						int num2 = this.outputWindow.CopyOutput(buffer, offset, count);
						if (num2 > 0)
						{
							this.adler.Update(buffer, offset, num2);
							offset += num2;
							num += num2;
							this.totalOut += (long)num2;
							count -= num2;
							if (count == 0)
							{
								goto Block_11;
							}
						}
					}
				}
				while (this.Decode() || (this.outputWindow.GetAvailable() > 0 && this.mode != 11));
				result = num;
				return result;
				Block_11:
				result = num;
			}
			return result;
		}
	}
}
